Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/phoenixframework/phoenix_live_view/llms.txt

Use this file to discover all available pages before exploring further.

The struct representing an upload configuration.

Overview

The Phoenix.LiveView.UploadConfig module defines the configuration structure for file uploads in LiveView. It is created by allow_upload/3 and manages upload entries, validation, and processing.

Type Definition

@type t :: %Phoenix.LiveView.UploadConfig{
  name: atom() | String.t(),
  cid: :unregistered | nil | integer(),
  client_key: String.t(),
  max_entries: pos_integer(),
  max_file_size: pos_integer(),
  entries: list(),
  entry_refs_to_pids: %{String.t() => pid() | :unregistered | :done},
  entry_refs_to_metas: %{String.t() => map()},
  accept: list() | :any,
  acceptable_types: MapSet.t(),
  acceptable_exts: MapSet.t(),
  external: function() | false,
  allowed?: boolean(),
  errors: list(),
  ref: String.t(),
  auto_upload?: boolean(),
  writer: function(),
  progress_event: function() | nil
}

Struct Fields

name
atom() | String.t()
The upload name provided to allow_upload/3
ref
String.t()
Unique reference for this upload configuration
entries
list(UploadEntry.t())
List of upload entries (files)
max_entries
pos_integer()
Maximum number of files allowed. Default: 1
max_file_size
pos_integer()
Maximum file size in bytes. Default: 8,000,000 (8MB)
chunk_size
pos_integer()
Size of chunks for upload streaming. Default: 64,000 bytes
chunk_timeout
pos_integer()
Timeout for chunk uploads in milliseconds. Default: 10,000ms
accept
list() | :any
List of accepted file types or :any. Examples: [".jpg", ".png", "image/*"]
acceptable_types
MapSet.t()
Set of acceptable MIME types
acceptable_exts
MapSet.t()
Set of acceptable file extensions
external
function() | false
External upload function for third-party storage, or false for local uploads
allowed?
boolean()
Whether the upload is currently allowed
errors
list()
List of validation errors as {ref, error} tuples
auto_upload?
boolean()
Whether files should be uploaded automatically. Default: false
progress_event
function() | nil
Optional callback for upload progress updates
writer
function()
Function that returns the writer module and options for handling uploads

Error Types

Upload validation can produce the following errors:
:too_large
atom
File exceeds max_file_size
:too_many_files
atom
Number of files exceeds max_entries
:not_accepted
atom
File type not in the accept list
:external_client_failure
atom
Error during external upload

Default Values

@default_max_entries 1
@default_max_file_size 8_000_000  # 8 MB
@default_chunk_size 64_000        # 64 KB
@default_chunk_timeout 10_000     # 10 seconds

Usage

The upload config is created with allow_upload/3:
def mount(_params, _session, socket) do
  socket = 
    socket
    |> allow_upload(:avatar,
      accept: ~w(.jpg .jpeg .png),
      max_entries: 1,
      max_file_size: 5_000_000
    )
  
  {:ok, socket}
end
Access the config in your assigns:
def render(assigns) do
  ~H"""
  <%= for entry <- @uploads.avatar.entries do %>
    <div>
      <%= entry.client_name %> - <%= entry.progress %>%
    </div>
  <% end %>
  
  <%= for err <- @uploads.avatar.errors do %>
    <div class="error"><%= error_to_string(err) %></div>
  <% end %>
  """
end

Accept Formats

The :accept option supports:

File Extensions

Must start with a period and have a known MIME type:
accept: ~w(.jpg .jpeg .png .gif)

MIME Types

accept: ["image/jpeg", "image/png"]

MIME Type Wildcards

accept: ["image/*", "video/*", "audio/*"]

Any File Type

accept: :any

External Uploads

For uploads to external storage (S3, GCS, etc.):
def mount(_params, _session, socket) do
  socket =
    socket
    |> allow_upload(:avatar,
      accept: ~w(.jpg .jpeg .png),
      max_entries: 1,
      external: &presign_upload/2
    )
  
  {:ok, socket}
end

defp presign_upload(entry, socket) do
  {:ok, meta, socket} = 
    Phoenix.LiveView.Upload.external_upload_meta(entry, socket)
  
  # Generate presigned URL for your storage service
  presigned_url = MyApp.Storage.generate_presigned_url(entry, socket)
  
  {:ok, %{uploader: "S3", url: presigned_url}, socket}
end

Custom Writer

Provide a custom writer for advanced upload handling:
def mount(_params, _session, socket) do
  socket =
    socket
    |> allow_upload(:avatar,
      accept: :any,
      writer: fn _name, _entry, _socket ->
        {MyApp.CustomUploadWriter, [bucket: "uploads"]}
      end
    )
  
  {:ok, socket}
end

Progress Callback

Track upload progress with a custom callback:
def mount(_params, _session, socket) do
  socket =
    socket
    |> allow_upload(:documents,
      accept: ["application/pdf"],
      progress: &handle_progress/3
    )
  
  {:ok, socket}
end

defp handle_progress(:documents, entry, socket) do
  if entry.done? do
    # Upload complete
    {:noreply, put_flash(socket, :info, "Upload complete!")}
  else
    # Still uploading
    {:noreply, socket}
  end
end

Auto Upload

Enable automatic upload on file selection:
def mount(_params, _session, socket) do
  socket =
    socket
    |> allow_upload(:avatar,
      accept: ~w(.jpg .jpeg .png),
      auto_upload: true
    )
  
  {:ok, socket}
end

Validation Example

def error_to_string(:too_large), do: "File is too large"
def error_to_string(:too_many_files), do: "You have selected too many files"
def error_to_string(:not_accepted), do: "File type not accepted"

def render(assigns) do
  ~H"""
  <form phx-change="validate" phx-submit="save">
    <.live_file_input upload={@uploads.avatar} />
    
    <%= for entry <- @uploads.avatar.entries do %>
      <div>
        <progress value={entry.progress} max="100"><%= entry.progress %>%</progress>
        <button type="button" phx-click="cancel-upload" phx-value-ref={entry.ref}>×</button>
      </div>
    <% end %>
    
    <%= for {_ref, error} <- @uploads.avatar.errors do %>
      <p class="error"><%= error_to_string(error) %></p>
    <% end %>
    
    <button type="submit">Upload</button>
  </form>
  """
end

def handle_event("validate", _params, socket) do
  {:noreply, socket}
end

def handle_event("cancel-upload", %{"ref" => ref}, socket) do
  {:noreply, cancel_upload(socket, :avatar, ref)}
end

def handle_event("save", _params, socket) do
  uploaded_files =
    consume_uploaded_entries(socket, :avatar, fn %{path: path}, entry ->
      dest = Path.join("uploads", entry.client_name)
      File.cp!(path, dest)
      {:ok, ~p"/uploads/#{entry.client_name}"}
    end)
  
  {:noreply, socket |> put_flash(:info, "Files uploaded!")}
end